/**
 * @fileoverview
 * This script defines a widget for fetching and displaying images from www.fly-ted.org.
 * @author <a href="http://purl.org/net/aliman">Alistair Miles</a>, Graham Klyne, Jun Zhao
 * @version $Revision:538 $ on $Date: 2008-08-27 09:08:41 +0100 (Wed, 27 Aug 2008) $ by $Author: aliman $
 * @requires YAHOO.util.Connect
 * @requires flyui
 * @requires flyui.flyted.Service
 * @requires flyui.flybase.Gene
 * TODO license terms
 */
 
// create a namespace if not already defined
flyui.namespace("flyui.flyted");


/*
 * --------------------------------------------------
 * WIDGET
 * --------------------------------------------------
 */


/**
 * Create a flyted image widget
 * @class
 * A widget for displaying images from www.fly-ted.org
 * @constructor
 * @param {flyui.flyted.Service} service the service to use to fetch data
 * @param {flyui.flyted.DefaultRenderer} renderer the renderer to use
 */
flyui.flyted.ImageWidget = function( service, renderer ) {

	try {
		var that = this;
		
		/** @private */
		this._controller = null;
		
		/** @private */
		this._renderer = renderer;
		
		this._model = null;
		
		this._renderer = null;
    
    	this._service = null;
		
		this._init = function() {
			// create a model
			var model = new flyui.mvcutils.GenericModel2();
			model.setDefinition(flyui.flyted.ImageWidget.modelDefinition);
			
			// instantiate the controller
			this._controller = new flyui.flyted.ImageWidget.Controller(model, service, this);
			
			// connect the renderer to the model
			renderer.connect(model);
		};
				
    	// do initialisation
    	this._init(service, renderer);
    } catch (error) {
        throw new flyui.UnexpectedException("flyui.flyted.ImageWidget", error);
    }	
};

//flyui.flyted.ImageWidget.prototype._init = function(service, renderer) {
//	try {
//		this._service = service;
//        this._renderer = renderer;
//        
//        // create a model
//        this._model = new flyui.mvcutils.GenericModel2();
//        this._model.setDefinition(flyui.flyted.ImageWidget.modelDefinition);
//        
//        // instantiate the controller
//        this._controller = new flyui.flyted.ImageWidget.Controller(this._model, service, this);
//        
//        // connect the renderer to the model
//        this._renderer.connect(this._model);
//		
//	}catch (error) {
//        throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.prototype._init", error);
//    }	
//}


/**
 * Find images by gene names used within fly-ted
 * @param {String} flytedGeneName a flyted gene name, see http://www.fly-ted.org/view/geneid/
 */
flyui.flyted.ImageWidget.prototype.findImagesByFlytedGeneName = function( flytedGeneName ) {
	// pass through to controller
	try {
		this._controller.findImagesByFlytedGeneName(flytedGeneName);
	}catch (error) {
        throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.prototype.findImagesByFlytedGeneName", error);
    }
};


/**
 * Find images by flybase gene ID, using gene name translation data
 * @param {String} flybaseGeneID a flybase gene ID, e.g. FBgn123456
 */
flyui.flyted.ImageWidget.prototype.findImagesByFlybaseGeneID = function( flybaseGeneID ) {
	try {
		// pass through to controller
		this._controller.findImagesByFlybaseGeneID(flybaseGeneID);
	}catch (error) {
        throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.prototype.findImagesByFlybaseGeneID", error);
    }
};


/**
 * Find images by a gene object, translating gene names as needed.
 * @param {flyui.flybase.Gene} gene
 */
flyui.flyted.ImageWidget.prototype.findImagesByGene = function( gene ) {
	try {
		// pass through to controller
		this._controller.findImagesByGene(gene);
	}catch (error) {
        throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.prototype.findImagesByGene", error);
    }
};


/*
 * --------------------------------------------------
 * CONTROLLER
 * --------------------------------------------------
 */


/**
 * Create a controller for a flyted image widget.
 * @class
 * A controller class for the flyted image widget internal MVC.
 * @constructor
 * @param {flyui.mvcutils.GenericModel2} model the model to store widget state data
 * @param {flyui.flyted.Service} service the service to use to fetch data
 * @param {flyui.flyted.ImageWidget} widget the widget to control
 */
flyui.flyted.ImageWidget.Controller = function( model, service, widget ) {
	
	var that = this;
	
	/**
	 * @private
	 */
	this._model = model;
	
	/**
	 * @private
	 */
	this._service = service;
	
	/**
	 * @private
	 */
	this._parent = widget;
	
	
	/**
	 * @private
	 * Success case callback.
	 * @param {Array<flyui.flyted.Image>} images
	 */	
	this._findImagesSuccess = function( images ) {
		try {
			flyui.info("request success");
		
			// set the results
			that._model.set("RESULTS", images);
			
			// set the state
			that._model.set("STATE", "READY");
		}catch (error) {
        	throw new flyui.UnexpectedException("_findImagesSuccess", error);
    	}

	};
	
	
	/**
	 * @private
	 * Failure case callback.
	 * @param {Object} response the HTTP response (YUI)
	 */	
	this._findImagesFailure = function( response ) {
		try {
			flyui.err("request failed, status "+response.status+" "+response.statusText);
			
			// set an error message
			var msg = "There was an error retrieving data from FlyTED, see the logs for more info. The server may be busy or down, please try again later. If this message persists, please contact the Image Bioinformatics Research Group at bioimage@mail.ontonet.org.";		
	
			that._model.set("ERRORMESSAGE", msg);
	
			// set the state
			that._model.set("STATE", "SERVERERROR");
		}catch (error) {
        	throw new flyui.UnexpectedException("_findImagesFailure", error);
    	}		
	};
	
};


/**
 * Find images by flyted gene name
 * @param {String} flytedGeneName a flyted gene name, see http://www.fly-ted.org/view/geneid/
 */
flyui.flyted.ImageWidget.Controller.prototype.findImagesByFlytedGeneName = function( flytedGeneName ) {
	try {
		// pass through to private implementation
		this._findImagesByFlytedGeneName(flytedGeneName, this._findImagesSuccess, this._findImagesFailure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.Controller.prototype.findImagesByFlytedGeneName", error);
    }
};


/**
 * @private
 * Implement find images by flyted gene name, allowing callback injection for testing
 * @param {String} flytedGeneName a flyted gene name, see http://www.fly-ted.org/view/geneid/
 * @param {Function} success success case callback
 * @param {Function} failure failure case callback
 */
flyui.flyted.ImageWidget.Controller.prototype._findImagesByFlytedGeneName = function( flytedGeneName, success, failure ) {
	try {
		flyui.info("flyui.flyted.ImageWidget.Controller._findImagesByFlytedGeneName :: request: "+flytedGeneName);
		
		// set the model pending
		this._model.set("STATE", "PENDING");
		
		// set the query property
		this._model.set("QUERY", flytedGeneName);
		
		// kick off the request
		this._service.findImagesByFlytedProbeName(flytedGeneName, success, failure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.Controller.prototype._findImagesByFlytedGeneName", error);
    }
};


/**
 * Find images by flybase gene ID
 * @param {String} flybaseGeneID a flybase gene ID, e.g. FBgn123456
 */
flyui.flyted.ImageWidget.Controller.prototype.findImagesByFlybaseGeneID = function( flybaseGeneID ) {
	try {
		// pass through to private implementation
		this._findImagesByFlybaseGeneID(flybaseGeneID, this._findImagesSuccess, this._findImagesFailure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.Controller.prototype.findImagesByFlybaseGeneID", error);
    }
};


/**
 * @private
 * Implement find images by flybase gene ID, with callback injection for testing.
 * @param {String} flybaseGeneID a flybase gene ID, e.g. FBgn123456
 * @param {Function} success success case callback
 * @param {Function} failure failure case callback
 */
flyui.flyted.ImageWidget.Controller.prototype._findImagesByFlybaseGeneID = function( flybaseGeneID, success, failure ) {
	try {
		flyui.info("flyui.flyted.ImageWidget.Controller._findImagesByFlybaseGeneID :: request: "+flybaseGeneID);
		
		// set the model pending
		this._model.set("STATE", "PENDING");
		
		// set the query property
		this._model.set("QUERY", flybaseGeneID);
		
		// kick off the request
		this._service.findImagesByFlybaseGeneID(flybaseGeneID, success, failure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.Controller.prototype._findImagesByFlybaseGeneID", error);
    }
};


/**
 * Find images by flybase gene object
 * @param {flyui.flybase.Gene} gene 
 */
flyui.flyted.ImageWidget.Controller.prototype.findImagesByGene = function( gene ) {
	try {
		// pass through to private implementation
		this._findImagesByGene(gene, this._findImagesSuccess, this._findImagesFailure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.Controller.prototype.findImagesByGene", error);
    }
};


/**
 * @private
 * Implement find images by flybase gene object, with callback injection to support testing
 * @param {flyui.flybase.Gene} gene 
 * @param {Function} success success case callback
 * @param {Function} failure failure case callback
 */
flyui.flyted.ImageWidget.Controller.prototype._findImagesByGene = function( gene, success, failure ) {
	try {
		flyui.info("flyui.flyted.ImageWidget.Controller._findImagesByGene :: request: "+gene.flybaseID);
	
		// set the model pending
		this._model.set("STATE", "PENDING");
		
		// set the query property
		this._model.set("QUERY", gene);
		
		// kick off the request
		this._service.findImagesByFlybaseGeneID(gene.flybaseID, success, failure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.Controller.prototype._findImagesByGene", error);
    }
};


/*
 * --------------------------------------------------
 * MODEL DEFINITION
 * --------------------------------------------------
 */


/**
 * Definition of flyted imagewidget model.
 */
flyui.flyted.ImageWidget.modelDefinition = {

	properties : [ "STATE", "RESULTS", "QUERY", "ERRORMESSAGE" ],
	
	values : {
		"STATE" : [ "PENDING", "READY", "SERVERERROR", "UNEXPECTEDERROR" ]
	},
	
	initialize : function( data ) {
		data["STATE"] = "READY";
		data["RESULTS"] = null;
		data["QUERY"] = null;
		data["ERRORMESSAGE"] = null;
	}

};


/*
 * --------------------------------------------------
 * DEFAULT RENDERER
 * --------------------------------------------------
 */


/**
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer = function() {};


/**
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype.setCanvas = function( canvas ) {
    try {
	    this._canvas = canvas;
	    this._initCanvas();
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype.setCanvas", error);
    }
};


/**
 * @private
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._initCanvas = function() {
	try {
	    YAHOO.util.Dom.addClass(this._canvas, "flytedImageWidget");
	    
	    // set up the pending pane
        this._pendingPane = document.createElement("p");
        this._pendingPane.innerHTML = "pending...";
        this._canvas.appendChild(this._pendingPane);
        YAHOO.util.Dom.addClass(this._pendingPane, "pendingPane");
        flyui.mvcutils.hide(this._pendingPane);
	    
	    // set up the message pane
	    this._messagePane = document.createElement("div");
	    this._messagePane.innerHTML = "this should never be displayed";
	    this._canvas.appendChild(this._messagePane);
	    YAHOO.util.Dom.addClass(this._messagePane, "messagePane");
	    flyui.mvcutils.hide(this._messagePane);
	    
	    // setup results summary pane
	    this._resultsSummaryPane = document.createElement("p");
	    this._canvas.appendChild(this._resultsSummaryPane);
	    YAHOO.util.Dom.addClass(this._resultsSummaryPane, "resultsSummaryPane");
	    flyui.mvcutils.hide(this._resultsSummaryPane);
	    
	    // setup results pane
	    this._resultsPane = document.createElement("div");
	    this._canvas.appendChild(this._resultsPane);
	    YAHOO.util.Dom.addClass(this._resultsPane, "resultsPane");
	    flyui.mvcutils.hide(this._resultsPane);
	    
	    // setup explanation pane
	    this._explanationPane = document.createElement("p");
	    this._explanationPane.innerHTML = "this should never be displayed";
	    this._canvas.appendChild(this._explanationPane);
        YAHOO.util.Dom.addClass(this._explanationPane, "explanationPane");
        flyui.mvcutils.hide(this._explanationPane);
	    
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype._initCanvas", error);
    }
};

/**
 * @private
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._canvas = null;

/**
 * @private
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._pendingPane = null;

/**
 * @private
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._resultsPane = null;

/**
 * @private
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._resultsSummaryPane = null;

/**
 * @private
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._messagePane = null;

/** @private */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._explanationPane = null;

/**
 * @private
 * Main callback function for model changes.
 * @param {String} type the name of the model property changed
 * @param {Array} args the callback args
 * @param {flyui.genefinder.DefaultRenderer} self a self reference, to work around callback issues
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._onModelChanged = function(type, args, self) {
    try {
	    var handlers = {
	        "STATE":"_onStateChanged",
	        "QUERY":"_onQueryChanged",
	        "RESULTS":"_onResultsChanged",
	        "ERRORMESSAGE":"_onErrorMessageChanged"
	    };
	    var handler = handlers[type];
	    flyui.debug("handler: "+handler);
	    // call the handler
	    self[handler](args[0], args[1], args[2]);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype._onModelChanged", error);
    }
};


/**
 * @private
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._onStateChanged = function( from, to, get ) {
    var _context = "flyui.flyted.ImageWidget.DefaultRenderer.prototype._onStateChanged";
    try {
	    if ( to == "PENDING" ) {
		    flyui.mvcutils.show(this._pendingPane);
		    flyui.mvcutils.hide(this._messagePane);
		    flyui.mvcutils.hide(this._resultsSummaryPane);
		    flyui.mvcutils.hide(this._resultsPane);
            flyui.mvcutils.hide(this._explanationPane);
		} 
		else if (to == "READY") {
		    flyui.mvcutils.hide(this._pendingPane);
		    flyui.mvcutils.hide(this._messagePane);
		    flyui.mvcutils.show(this._resultsSummaryPane);
		    var results = get("RESULTS");
		    flyui.debug("results: "+results, _context);
		    flyui.debug("results length: "+results.length, _context)
		    if (results.length>0) {
                flyui.mvcutils.show(this._resultsPane);     
                flyui.mvcutils.show(this._explanationPane);
		    }
		}
		else if ( to == "SERVERERROR" || to == "UNEXPECTEDERROR" ) {
		    flyui.mvcutils.hide(this._pendingPane);
		    flyui.mvcutils.show(this._messagePane);
		    flyui.mvcutils.hide(this._resultsSummaryPane);
		    flyui.mvcutils.hide(this._resultsPane);         
            flyui.mvcutils.hide(this._explanationPane);
		} 
		else {
		    // this should never happen
	    	throw {name:"flyui.flyted.ImageWidget.UnexpectedStateError", message:"Invalid state: "+newState};
		}
	} catch (error) {
        	throw new flyui.UnexpectedException(_context, error);
    }
};


/**
 * @private
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._onQueryChanged = function( from, to ) {
	try {
    	// store query
    	this._query = to;
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype._onQueryChanged", error);
    }
};


/**
 * @private
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._onResultsChanged = function( from, to ) {
    var _context = "flyui.flyted.ImageWidget.DefaultRenderer.prototype._onResultsChanged";	
	try {
        flyui.debug("empty results summary and explanation panes");
        this._resultsPane.innerHTML = "";
        this._resultsSummaryPane.innerHTML = "";
        this._explanationPane.innerHTML = "";

        flyui.debug("collect probes in results", _context);
		var probes = [];
		for (var i=0; i<to.length; i++) {
		    flyui.util.appendIfNotMember(probes, to[i].probe);
		}
		
	    flyui.debug("render the results summary", _context);
	    this._renderResultsSummary(this._query, to.length, probes);
	    
	    if (to.length > 0) {

	        flyui.debug("render the results "+to.length, _context);
	        this._resultsPane.innerHTML = this._imagesToDivHTML(to); 
	
	    }
	    else {
//	        flyui.info("hide results and explanation panes", _context);
//	        flyui.mvcutils.hide(this._resultsPane);
//            flyui.mvcutils.hide(this._explanationPane);
	    }
	} catch (error) {
        	throw new flyui.UnexpectedException(_context, error);
    }

};
  
  
/**
 * @private
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._onErrorMessageChanged = function( from, to) {
    try {
    	this._messagePane.innerHTML = to;
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype._onErrorMessageChanged", error);
    }
};


/**
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype.connect = function( model ) {
	try {
    	model.subscribeAll(this._onModelChanged, this);
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype.connect", error);
    }

};


/**
 * @private
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._renderResultsSummary = function( query, count, probes ) {
    var _context = "flyui.flyted.ImageWidget.DefaultRenderer.prototype._renderResultsSummary";
	try {
	    function linkify(l) {
	        return l.replace("/","=2F").replace(" ","_");
	    }
	    
		flyui.debug("building results summary content", _context);
        var explanation = "";
	    var content = "found <span>";
	    content += count;
	    content += "</span> matching image";
	    content += (count == 0 || count > 1) ? "s " : " ";
	    content += "from <a href='http://www.fly-ted.org'>www.fly-ted.org</a> for ";
	    
	    var labels = [];
	    for (var i=0; i<probes.length; i++) {
	        var probe = probes[i];
	        for (var j=0; j<probe.probeLabels.length; j++) {
	            flyui.util.appendIfNotMember(labels, probe.probeLabels[j]);
	        }
	    }
	    
	    if (query instanceof flyui.flybase.Gene) {
	    	flyui.debug("a flybaseid in the query: " +query, _context);
	        content += "gene <strong>"+query.symbols.join(" / ")+"</strong>";
	        
	    } else {
	    	flyui.debug("not a flybaseid in the query :"+ query, _context);
	        content +=  "query <span>'"+query+"'</span>";
	    }

        if (labels.length > 0) {
            content += " (Fly-TED report";
            content += (labels.length>1)? "s:" : ":";
            for (var i=0; i<labels.length; i++) {
                var l = labels[i];
                content += " <a href='http://www.fly-ted.org/view/geneid/"+linkify(l)+".html'>"+l+"</a>";
                if (i<labels.length-1) {
                    content += ",";
                }                
            }
            content += ")...";

            flyui.debug("now generate explanation", _context);
            if (probes.length == 1 && probes[0].probeLabels.length>1) {
                flyui.debug("deal with probe synonyms", _context);
                explanation += "N.B. the reports at www.fly-ted.org (";
                for (var i=0; i<probes[0].probeLabels.length; i++) {
                    var l = probes[0].probeLabels[i];
                    explanation += "<a href='http://www.fly-ted.org/view/geneid/"+linkify(l)+".html'>"+l+"</a>";
                    if (i<probes[0].probeLabels.length-1) {
                        explanation += ", ";
                    }
                }
                explanation += ") report data on <strong>the same probe</strong>; check probe PCR sequence to verify";
            } else if (probes.length > 1) {
                flyui.debug("deal with multiple hybridisation", _context);
                explanation += "N.B. the reports at www.fly-ted.org (";
                for (var i=0; i<probes.length; i++) {
                    var l = probes[i].probeLabels[0];
                    explanation += "<a href='http://www.fly-ted.org/view/geneid/"+linkify(l)+".html'>"+l+"</a>";
                    if (i<probes.length-1) {
                        explanation += ", ";
                    }
                }
                explanation += ") report data on <strong>different probes</strong> which both amplify transcripts of gene "+query.flybaseID+"; check probe PCR sequence to verify";
            } else {
                flyui.err("encountered unexpected probe / labels combination", _context);
            }

        }
	    this._resultsSummaryPane.innerHTML = content;
	    this._explanationPane.innerHTML = explanation;
	    
	    
	} catch (error) {
        	throw new flyui.UnexpectedException(_context, error);
    }
};

/**
 * @private
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._imagesToDivHTML = function( images ) {

    try {
	    // build the divs
	    
	    flyui.debug("build div content for images "+images.length);
	    var content = "";
	    for ( var i in images ) {
	        
	        content += this._imageToDivHTML(images[i]);
	        flyui.debug("Generate the img div" + content); 
	    }
	    
	    return content;
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype._imagesToDivHTML", error);
    }
};


/**
 * @private
 * TODO doc me
 */
flyui.flyted.ImageWidget.DefaultRenderer.prototype._imageToDivHTML = function( image ) {
  	try {
	    flyui.debug("build content for image "+image.caption);
	    
	    var content =   "<div class=\"result\">";
	    content +=          "<p><a href=\"" + image.flytedURL + "\">";
	    content +=              "<img src=\"" + image.thumbnailURL + "\" alt=\"this is an image\">";
	    content +=          "</img></a></p>"; 
	    content +=          "<p class=\"caption\">"+image.caption+"</p>"
	    content +=      "</div>";
	    
	    return content;
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.flyted.ImageWidget.DefaultRenderer.prototype._imageToDivHTML", error);
    }
};

